namespace CacheProvider
{
    #region N A M E S P A C E   I M P O R T S

    using System;
    using System.Collections.Generic;
    using Microsoft.Practices.Composite.Events;
    using Microsoft.Practices.Composite.Presentation.Events;

    #endregion

    public class CallbackManager
    {
        #region P R I V A T E   A T T R I B U T E S

        //holds the callback subscriptions
        private readonly List<IEventSubscription> _subscriptions = new List<IEventSubscription>();

        //Used to serialize access to the _subscriptions list.
        private static readonly object SubscriptionsSerializer = new object();

        //holds the payload to be used during callback.
        private object _payload;

        #endregion

        #region P U B L I C   M E T H O D S

        /// <summary>
        /// Registers a callback delegate that can be invoked at a later stage. 
        /// </summary>
        /// <typeparam name="TState">Type of Payload with which the callback should be invoked.</typeparam>
        /// <param name="callback">The callback delegate</param>
        /// <param name="payload">Payload instance with which the callback should be invoked.</param>
        /// <param name="threadOption">Thread on which the callback should be invoked. See <see cref="ThreadOption"/> for details</param>
        /// <param name="keepSubscriberReferenceAlive">Should there be a strong or weak reference to the callback.</param>
        /// <returns>an instance of <see cref="SubscriptionToken"/></returns>
        public SubscriptionToken RegisterCallback<TState>(Action<TState> callback,
                                                          TState payload,
                                                          ThreadOption threadOption,
                                                          bool keepSubscriberReferenceAlive)
        {
            IDelegateReference actionReference = new DelegateReference(callback, keepSubscriberReferenceAlive);

            IDelegateReference filterReference = new DelegateReference(new Predicate<TState>(delegate { return true; }), true);

            EventSubscription<TState> subscription;

            switch (threadOption)
            {
                case ThreadOption.PublisherThread:
                    subscription = new EventSubscription<TState>(actionReference, filterReference, false);
                    break;
                case ThreadOption.BackgroundThread:
                    subscription = new BackgroundEventSubscription<TState>(actionReference, filterReference);
                    break;
                case ThreadOption.UIThread:
                    subscription = new EventSubscription<TState>(actionReference, filterReference, true);
                    break;
                default:
                    subscription = new EventSubscription<TState>(actionReference, filterReference, false);
                    break;
            }

            subscription.SubscriptionToken = new SubscriptionToken();

            lock (SubscriptionsSerializer)
            {
                _subscriptions.Add(subscription);
            }

            this._payload = payload;

            return subscription.SubscriptionToken;
        }

        /// <summary>
        /// Invokes the registered callbacks.
        /// </summary>
        public void InvokeCallbacks()
        {
            var arguments = new[] { this._payload };
            var executionStrategies = PruneAndReturnStrategies();

            foreach (var executionStrategy in executionStrategies)
            {
                executionStrategy(arguments);
            }
        }

        #endregion

        #region P R I V A T E   M E T H O D S

        private List<Action<object[]>> PruneAndReturnStrategies()
        {
            var returnList = new List<Action<object[]>>();

            lock (SubscriptionsSerializer)
            {
                for (var i = _subscriptions.Count - 1; i >= 0; i--)
                {
                    var listItem = _subscriptions[i].GetExecutionStrategy();

                    if (listItem == null)
                    {
                        _subscriptions.RemoveAt(i);
                    }
                    else
                    {
                        returnList.Add(listItem);
                    }
                }
            }

            return returnList;
        }

        #endregion
    }
}